/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.jdbc.init;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import javax.sql.DataSource;

import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * 工具类，用于将占位符文本解析为实际的 {@link DatabaseDriver} 平台名称。
 * <p>
 * 默认情况下，平台名称是驱动的 {@link DatabaseDriver#getId ID}。该映射可以通过
 * {@link #withDriverPlatform(DatabaseDriver, String)} 注册自定义的
 * {@code DatabaseDriver} 到平台的映射关系来进行定制。
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 2.6.0
 */
public class PlatformPlaceholderDatabaseDriverResolver {

	private final String placeholder;

	private final Map<DatabaseDriver, String> driverMappings;

	/**
	 * 创建一个使用默认占位符 {@code "@@platform@@"} 的解析器。
	 */
	public PlatformPlaceholderDatabaseDriverResolver() {
		this("@@platform@@");
	}

	/**
	 * 创建一个使用指定占位符的解析器。
	 * @param placeholder 要使用的占位符
	 */
	public PlatformPlaceholderDatabaseDriverResolver(String placeholder) {
		this(placeholder, Collections.emptyMap());
	}

	private PlatformPlaceholderDatabaseDriverResolver(String placeholder, Map<DatabaseDriver, String> driverMappings) {
		this.placeholder = placeholder;
		this.driverMappings = driverMappings;
	}

	/**
	 * 创建一个新的解析器，将给定的 {@code driver} 映射为指定的 {@code platform}。
	 * @param driver 数据库驱动
	 * @param platform 平台名称
	 * @return 新的解析器实例
	 */
	public PlatformPlaceholderDatabaseDriverResolver withDriverPlatform(DatabaseDriver driver, String platform) {
		Map<DatabaseDriver, String> driverMappings = new LinkedHashMap<>(this.driverMappings);
		driverMappings.put(driver, platform);
		return new PlatformPlaceholderDatabaseDriverResolver(this.placeholder, driverMappings);
	}

	/**
	 * 解析给定 {@code values} 中的占位符，将其替换为从指定 {@code dataSource} 的
	 * {@link DatabaseDriver} 获取的实际平台名称。
	 * @param dataSource 用于确定数据库驱动的数据源
	 * @param values 要解析占位符的字符串数组
	 * @return 占位符已被解析替换后的字符串列表
	 * @see DatabaseDriver#fromDataSource(DataSource)
	 */
	public List<String> resolveAll(DataSource dataSource, String... values) {
		Assert.notNull(dataSource, "DataSource must not be null");
		return resolveAll(() -> determinePlatform(dataSource), values);
	}

	/**
	 * 解析给定 {@code values} 中的占位符，将其替换为指定的平台名称。
	 * @param platform 平台名称
	 * @param values 要解析占位符的字符串数组
	 * @return 占位符已被解析替换后的字符串列表
	 * @since 2.6.2
	 */
	public List<String> resolveAll(String platform, String... values) {
		Assert.notNull(platform, "Platform must not be null");
		return resolveAll(() -> platform, values);
	}

	private List<String> resolveAll(Supplier<String> platformProvider, String... values) {
		if (ObjectUtils.isEmpty(values)) {
			return Collections.emptyList();
		}
		List<String> resolved = new ArrayList<>(values.length);
		String platform = null;
		for (String value : values) {
			if (StringUtils.hasLength(value)) {
				if (value.contains(this.placeholder)) {
					platform = (platform != null) ? platform : platformProvider.get();
					value = value.replace(this.placeholder, platform);
				}
			}
			resolved.add(value);
		}
		return Collections.unmodifiableList(resolved);
	}

	private String determinePlatform(DataSource dataSource) {
		DatabaseDriver databaseDriver = getDatabaseDriver(dataSource);
		Assert.state(databaseDriver != DatabaseDriver.UNKNOWN, "Unable to detect database type");
		return this.driverMappings.getOrDefault(databaseDriver, databaseDriver.getId());
	}

	DatabaseDriver getDatabaseDriver(DataSource dataSource) {
		return DatabaseDriver.fromDataSource(dataSource);
	}

}
